home *** CD-ROM | disk | FTP | other *** search
/ CD ROM Paradise Collection 4 / CD ROM Paradise Collection 4 1995 Nov.iso / program / swagd_f.zip / DIRS.SWG / 0029_Directory Object.pas < prev    next >
Pascal/Delphi Source File  |  1994-05-25  |  13KB  |  485 lines

  1. {
  2.   Next in this continuing series of code: the actual directry
  3.   object.
  4. }
  5.  
  6. Unit Dirs;
  7. {
  8.   A directory management object from a concept originally by Allan
  9.   Holub, as discussed in Byte Dec/93 (Vol 18, No 13, page 213)
  10.  
  11.   Turbo Pascal code by Larry Hadley, tested using BP7.
  12. }
  13. INTERFACE
  14.  
  15. Uses Sort, DOS;
  16.  
  17. TYPE
  18.    pSortSR = ^oSortSR;
  19.    oSortSR = OBJECT(oSortTree)
  20.       procedure   DeleteNode(var Node); virtual;
  21.    end;
  22.  
  23.    callbackproc = procedure(name :string; lev :integer);
  24.  
  25.    prec  = ^searchrec;
  26.  
  27.    pentry = ^entry;
  28.    entry  = record
  29.       fil          :prec;
  30.       next, last   :pentry;
  31.    end;
  32.  
  33.    pdir  = ^dir;
  34.    dir   = record
  35.       flist  :pentry;
  36.       count  :word;
  37.       path   :string[80];
  38.    end;
  39.  
  40.    pDirectry = ^Directry;
  41.    Directry  = OBJECT
  42.       dirroot   :pdir;
  43.  
  44.       constructor Init(path, filespec :string; attribute :byte);
  45.       destructor  Done;
  46.  
  47.       procedure   Load(path, filespec :string; attribute :byte);
  48.       procedure   Sort;
  49.       procedure   Print;
  50.    END;
  51.  
  52. CONST
  53.    NotDir  = ReadOnly+Hidden+SysFile+VolumeID+Archive;
  54.    dosattr : array[0..8] of char = '.rhsvdaxx';
  55.  
  56. procedure TraverseTree(root :string; pcallproc :pointer; do_depth :boolean);
  57.  
  58. IMPLEMENTATION
  59.  
  60. var
  61.    treeroot :pSortSR; { sorting tree object }
  62.  
  63. procedure disposelist(ls :pentry);
  64. var
  65.    lso :pentry;
  66. begin
  67.    while ls<>NIL do
  68.    begin
  69.       dispose(ls^.fil);
  70.       lso := ls;
  71.       ls := ls^.next;
  72.       dispose(lso);
  73.    end;
  74. end;
  75.  
  76. { Define oSortSR.DeleteNode method so object knows how to dispose of
  77.   individual data pointers in the event that "Done" is called before
  78.   tree is empty. }
  79. procedure   oSortSR.DeleteNode(var Node);
  80. var
  81.    pNode :pRec ABSOLUTE Node;
  82. begin
  83.    dispose(pNode);
  84. end;
  85.  
  86. constructor Directry.Init(path, filespec :string; attribute :byte);
  87. var
  88.    pathspec :string;
  89.    node     :pentry;
  90.    i        :word;
  91. BEGIN
  92.    FillChar(Self, SizeOf(Self), #0);
  93.    Load(path, filespec, attribute); { scan specified directory }
  94.    if dirroot^.count=0 then         { if no files were found, abort }
  95.    begin
  96.       if dirroot<>NIL then
  97.       begin
  98.          disposelist(dirroot^.flist);
  99.          dispose(dirroot);
  100.       end;
  101.       FAIL;
  102.    end;
  103.  { the following code expands the pathspec to a full qualified path }
  104.    pathspec := dirroot^.path+'\';
  105.    node := dirroot^.flist;
  106.    while ((node^.fil^.name='.') or (node^.fil^.name='..')) and
  107.          (node^.next<>NIL) do
  108.       node := node^.next;
  109.    if node^.fil^.name='..' then
  110.       pathspec := pathspec+'.'
  111.    else
  112.       pathspec := pathspec+node^.fil^.name;
  113.    pathspec := FExpand(pathspec);
  114.    i := Length(pathspec);
  115.    repeat
  116.       Dec(i);
  117.    until (i=0) or (pathspec[i]='\');
  118.    if i>0 then
  119.    begin
  120.       Delete(pathspec, i, Length(pathspec));
  121.       dirroot^.path := pathspec;
  122.    end;
  123. END;
  124.  
  125. destructor  Directry.Done;
  126. begin
  127.    if dirroot<>NIL then
  128.    begin
  129.       disposelist(dirroot^.flist);
  130.       dispose(dirroot);
  131.    end;
  132. end;
  133.  
  134. procedure   Directry.Load(path, filespec :string; attribute :byte);
  135. { scan a specified directory with a specified wildcard and attribute
  136.   byte }
  137. var
  138.    count   : word;
  139.    pstr    : pathstr;
  140.    dstr    : dirstr;
  141.    srec    : SearchRec;
  142.    dirx    : pdir;
  143.    firstfl, thisfl, lastfl  : pentry;
  144. begin
  145.    count := 0;
  146.    New(firstfl);
  147.    with firstfl^ do
  148.    begin
  149.       next := NIL; last := NIL; New(fil);
  150.    end;
  151.    thisfl := firstfl; lastfl := firstfl;
  152.    dstr  := path;
  153.    if path = '' then dstr := '.';
  154.    if dstr[Length(dstr)]<>'\' then dstr := dstr+'\';
  155.    if filespec = '' then filespec := '*.*';
  156.    pstr := dstr+filespec;
  157.  
  158.    FindFirst(pstr, attribute, srec);
  159.    while DosError=0 do { while new files are found... }
  160.    begin
  161.       if srec.attr = (srec.attr and attribute) then
  162.  { make sure the attribute byte matches our required atttribute mask }
  163.       begin
  164.          if count>0 then
  165.  { if this is NOT first file found, link in new node }
  166.          begin
  167.             New(thisfl);
  168.             lastfl^.next := thisfl;
  169.             thisfl^.last := lastfl;
  170.             thisfl^.next := NIL;
  171.             New(thisfl^.fil);
  172.             lastfl := thisfl;
  173.          end;
  174.          thisfl^.fil^ := srec;
  175.          Inc(count);
  176.       end;
  177.       FindNext(srec);
  178.    end;
  179.  { construct root node }
  180.    New(dirx);
  181.    with dirx^ do
  182.       flist := firstfl;
  183.    dirx^.path  := path;  { path specifier for directory list }
  184.    dirx^.count := count; { number of files in the list }
  185.  
  186.    if dirroot=NIL then
  187.       dirroot := dirx
  188.    else
  189.    begin
  190.       disposelist(dirroot^.flist);
  191.       dispose(dirroot);
  192.       dirroot := dirx;
  193.    end;
  194. end;
  195.  
  196. { The following function is the far-local function needed for the
  197.   SORT method (which uses the sort unit posted earlier)
  198.   Note that this is hard-coded to sort by filename, then extension.
  199.   I plan to rewrite this later to allow user-selectable sort
  200.   parameters and ordering. }
  201. function Comp(d1, d2 :pointer):integer; far;
  202.    var
  203.       data1 :pRec ABSOLUTE d1;
  204.       data2 :pRec ABSOLUTE d2;
  205.       name1, name2, ext1, ext2  :string;
  206.    begin
  207.  { This assures that the '.' and '..' dirs will always be the first
  208.    listed. }
  209.       if (data1^.name='.') or (data1^.name='..') then
  210.       begin
  211.          Comp := -1;
  212.          EXIT;
  213.       end;
  214.       if (data2^.name='.') or (data2^.name='..') then
  215.       begin
  216.          Comp := 1;
  217.          EXIT;
  218.       end;
  219.       with data1^ do
  220.       begin
  221.          name1 := Copy(name, 1, Pos('.', name)-1);
  222.          ext1  := Copy(name, Pos('.', name)+1, 3);
  223.       end;
  224.       with data2^ do
  225.       begin
  226.          name2 := Copy(name, 1, Pos('.', name)-1);
  227.          ext2  := Copy(name, Pos('.', name)+1, 3);
  228.       end;
  229.       if name1=name2 then
  230.  { If filename portion is equal, use extension to resolve tie }
  231.       begin
  232.          if ext1=ext2 then
  233.  { There should be NO equal filenames, but handle anyways for
  234.    completeness... }
  235.             Comp := 0
  236.          else
  237.             if ext1>ext2 then
  238.                Comp := 1
  239.             else
  240.                Comp := -1;
  241.       end
  242.       else
  243.          if name1>name2 then
  244.             Comp := 1
  245.          else
  246.             Comp := -1;
  247.    end;
  248.  
  249. { Sort method uses the sort unit to sort the collected directory
  250.   entries. }
  251. procedure   Directry.Sort;
  252. var
  253.    s1, s2 :string;
  254.    p1     :pentry;
  255.  
  256.  { This local procedure keeps code more readable }
  257.    procedure UpdatePtr(var prev :pentry; NewEntry :pointer);
  258.    begin
  259.       if NewEntry<>NIL then { check to see if tree is empty }
  260.       begin
  261.          New(prev^.next);
  262.          prev^.next^.fil  := NewEntry;
  263.          prev^.next^.last := prev;
  264.          prev := prev^.next;
  265.          prev^.next := NIL;
  266.       end
  267.       else
  268.          prev := prev^.next;
  269.        { tree is empty, flag "done" with NIL pointer }
  270.    end;
  271.  
  272. begin
  273.    p1 := dirroot^.flist;
  274.    New(treeroot, Init(Comp));
  275. { Create a sort tree, point to our COMP function }
  276.    while p1<>NIL do
  277. { Go through our linked list and insert the items into the sorting
  278.   tree, dispose of original nodes as we go. }
  279.    begin
  280.       if p1^.last<>NIL then
  281.          dispose(p1^.last);
  282.       treeroot^.InsertNode(p1^.fil);
  283.       if p1^.next=NIL then
  284.       begin
  285.          dispose(p1);
  286.          p1 := NIL;
  287.       end
  288.       else
  289.          p1 := p1^.next;
  290.    end;
  291. { Reconstruct directory list from sorted tree }
  292.    New(dirroot^.flist);
  293.    with dirroot^ do
  294.    begin
  295.       flist^.next := NIL;
  296.       flist^.last := NIL;
  297.       flist^.fil := treeroot^.ReadLeftNode;
  298.    end;
  299.    if dirroot^.flist^.fil<>NIL then
  300.    begin
  301.       p1 := dirroot^.flist;
  302.       while p1<>NIL do
  303.          UpdatePtr(p1, treeroot^.ReadLeftNode);
  304.    end;
  305. { We're done with sorting tree... }
  306.    dispose(treeroot, Done);
  307. end;
  308.  
  309. procedure   Directry.Print;
  310. { currently prints the entire list, may modify this later to allow
  311.   selective printing }
  312. var
  313.    s, s1 :string;
  314.    e     :pentry;
  315.    dt    :DateTime;
  316.    dbg   :byte;
  317.  
  318.    procedure DoDateEle(var sb :string; de :word);
  319.    begin
  320.       Str(de, sb);
  321.       if Length(sb)=1 then { Add leading 0's}
  322.          sb := '0'+sb;
  323.    end;
  324.  
  325. begin
  326.    if dirroot=NIL then EXIT; { make sure empty dirs aren't attempted }
  327.    e := dirroot^.flist;
  328.    while e<>NIL do
  329.    begin
  330.       s := '';
  331.       with e^.fil^ do
  332.       begin
  333.          dbg := 1;
  334.          repeat
  335.             case dbg of { parse attribute bits }
  336.               1: s := s+dosattr[(attr and $01)];
  337.               2: s := s+dosattr[(attr and $02)];
  338.               3: if (attr and $04) = $04 then
  339.                     s := s+dosattr[3]
  340.                  else
  341.                     s := s+dosattr[0];
  342.               4: if (attr and $08) = $08 then
  343.                     s := s+dosattr[4]
  344.                  else
  345.                     s := s+dosattr[0];
  346.               5: if (attr and $10) = $10 then
  347.                     s := s+dosattr[5]
  348.                  else
  349.                     s := s+dosattr[0];
  350.               6: if (attr and $20) = $20 then
  351.                     s := s+dosattr[6]
  352.                  else
  353.                     s := s+dosattr[0];
  354.               else
  355.                  s := s+dosattr[0];
  356.             end;
  357.             Inc(dbg);
  358.          until dbg>8;
  359.          s := s+' ';
  360.    { Kludge to make sure that extremely large files (>=100MB) don't
  361.      overflow size field... }
  362.          if size<100000000 then
  363.             Str(size:8, s1)
  364.          else
  365.          begin
  366.             Str((size div 1000):7, s1); { decimal kilobytes }
  367.             s1 := s1+'k';
  368.          end;
  369.          s := s+s1+' ';
  370.    { Format date/time fields }
  371.          UnpackTime(Time, dt);
  372.          {month}
  373.          DoDateEle(s1, dt.month); s := s+s1+'/';
  374.          {day}
  375.          DoDateEle(s1, dt.day);   s := s+s1+'/';
  376.          {year}
  377.          DoDateEle(s1, dt.year);  s := s+s1+' ';
  378.          {hour}
  379.          DoDateEle(s1, dt.hour);  s := s+s1+':';
  380.          {minutes}
  381.          DoDateEle(s1, dt.min);   s := s+s1+':';
  382.          {seconds}
  383.          DoDateEle(s1, dt.sec);   s := s+s1+' - ';
  384.          s := s+dirroot^.path+'\'+name;
  385.       end;
  386.       Writeln(s); s := '';
  387.       e := e^.next;
  388.    end;
  389.    Writeln; Writeln('  ', dirroot^.count, ' files found.'); Writeln;
  390. end;
  391.  
  392. { If TraverseTree is not given a callback procedure, this one is
  393.   used. }
  394. procedure   DefaultCallback(name :string; lev :integer); far;
  395. var
  396.    s :string;
  397. const
  398.    spaces = '                                               ';
  399. begin
  400.    s := Copy(spaces, 1, lev*4); s := s+name;
  401.    Writeln(s);
  402. end;
  403.  
  404. { TraverseTree is untested as yet, rest of code (above) works fine.
  405.   Note that TraverseTree is NOT a member method of DIRECTRY. Read
  406.   the BYTE Dec/93 article for a clarification of why it is good
  407.   that it not be a member.}
  408. procedure TraverseTree(root :string; pcallproc :pointer; do_depth :boolean);
  409. var
  410.    level    :integer;
  411.    fullpath :string;
  412.    rootdir  :pdir;
  413. const
  414.    callproc : callbackproc = DefaultCallBack;
  415.  
  416.  { Actual recursive procedure to scan down directory structure
  417.    using the DIRECTRY object. }
  418.    procedure Tree(newroot :string; callee :callbackproc; do_last :boolean);
  419.    var
  420.       subdirs  :pdirectry;
  421.       direntry :pentry;
  422.  
  423.       Procedure DoDir;
  424.       begin
  425.          New(subdirs, Init(newroot, '*.*', NotDir));
  426.          if subdirs<>NIL then
  427.          begin
  428.             subdirs^.sort;
  429.             direntry := subdirs^.dirroot^.flist;
  430.             while direntry<>NIL do
  431.             begin
  432.                fullpath := newroot+'\'+direntry^.fil^.name;
  433.                callee(newroot, level);
  434.                direntry := direntry^.next;
  435.             end;
  436.             dispose(subdirs, done);
  437.          end;
  438.       end;
  439.  
  440.    begin
  441.       if not(do_last) then
  442.          DoDir;
  443.  
  444.       New(subdirs, Init(newroot, '*.*', directory));
  445.  
  446.       if subdirs<>NIL then
  447.       begin
  448.          subdirs^.sort;
  449.          direntry := subdirs^.dirroot^.flist;
  450.          while direntry<>NIL do
  451.          begin
  452.             Inc(level);
  453.             fullpath := newroot+'\'+direntry^.fil^.name;
  454.             Tree(fullpath, callee, do_last);
  455.             dec(level);
  456.             direntry := direntry^.next;
  457.          end;
  458.          dispose(subdirs, done);
  459.       end;
  460.  
  461.       if do_last then
  462.          DoDir;
  463.    end;
  464.  
  465. begin
  466.    level := 0;
  467.  
  468.    if pcallproc<>NIL then
  469.       callproc := callbackproc(pcallproc^);
  470.  
  471.    root := fexpand(root);
  472.    if root[Length(root)]='\' then
  473.       Delete(root, Length(root), 1);
  474.  
  475.    if not(do_depth) then
  476.       callproc(root, level);
  477.  
  478.    Tree(root, callproc, do_depth);
  479.  
  480.    if do_depth then
  481.       callproc(root, level);
  482. end;
  483.  
  484. END.
  485.